package com.mongo.service.impl;

import com.mongo.func.SFunction;
import com.mongo.service.BaseService;
import com.mongo.service.LambdaMongoQuery;
import com.mongo.service.MongoMapper;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.objenesis.instantiator.util.ClassUtils;
import org.springframework.util.Assert;

import java.lang.reflect.Field;
import java.time.Duration;
import java.util.*;
import java.util.regex.Pattern;
import java.util.stream.Stream;

import static com.mongo.util.ReflectionUtil.findField;


/**
 * mongo的lambda查询实现
 * @author xiazhun
 * @create 2022/5/26
 */
@SuppressWarnings("all")
public class LambdaQuery<T> extends BaseService<T> implements LambdaMongoQuery<T> {
    private final Query query;

    private final List<Criteria> criterias;

    public LambdaQuery() {
        this((T) (null));
    }

    public LambdaQuery(MongoMapper<T> mongoMapper){
        setBaseMapper(mongoMapper);
        query = new Query();
        criterias = new ArrayList<>();
    }


    public LambdaQuery(T entity) {
        query = new Query();
        criterias = new ArrayList<>();
        setEntity(entity);
    }

    public LambdaQuery(Class<T> var) {
        this(ClassUtils.newInstance(var));
    }


    @Override
    public LambdaQuery<T> include(String... fields) {
        for (String field : fields) {
            query.fields().include(field);
        }
        return this;
    }

    public LambdaQuery<T> include(SFunction<T, ?> function) {
        Field field = findField(function);
        Assert.notNull(field, "无法获取指定字段");
        query.fields().include(field.getName());
        return this;
    }

    @Override
    public LambdaQuery<T> exclude(String... fields) {
        for (String field : fields) {
            query.fields().exclude(field);
        }
        return this;
    }

    public LambdaQuery<T> exclude(SFunction<T, ?> function) {
        Field field = findField(function);
        Assert.notNull(field, "无法获取指定字段");
        query.fields().exclude(field.getName());
        return this;
    }

    public LambdaQuery<T> desc(SFunction<T, ?> function) {
        return desc(true, function);
    }

    public LambdaQuery<T> desc(boolean condition, SFunction<T, ?> function) {
        if (condition) {
            Field field = findField(function);
            Assert.notNull(field, "无法获取引用字段");
            this.query.with(Sort.by(Sort.Order.desc(field.getName())));
        }
        return this;
    }

    @Override
    public LambdaQuery<T> asc(SFunction<T, ?> function) {
        return asc(true, function);
    }

    @Override
    public LambdaQuery<T> asc(boolean condition, SFunction<T, ?> function) {
        if (condition) {
            Field field = findField(function);
            Assert.notNull(field, "无法获取引用字段");
            this.query.with(Sort.by(Sort.Order.asc(field.getName())));
        }
        return this;
    }

    @Override
    public LambdaQuery<T> between(SFunction<T, ?> function, Object var1, Object var2) {
        return between(true, function, var1, var2);
    }

    @Override
    public LambdaQuery<T> between(boolean condition, SFunction<T, ?> function, Object var1, Object var2) {
        if (condition) {
            Field field = findField(function);
            Assert.notNull(field, "无法获取引用字段");
            String name = field.getName();
            if (Stream.of(var1, var2).anyMatch(Objects::nonNull)) {
                if (Objects.nonNull(var1) && Objects.nonNull(var2)) {
                    Criteria cri1 = Criteria.where(name).gte(var1).lte(var2);
                    setCriteria(cri1);
                } else {
                    Object o = Optional.ofNullable(var1).orElse(var2);
                    if (o.equals(var1)) {
                        setCriteria(Criteria.where(name).gte(var1));
                    } else {
                        setCriteria(Criteria.where(name).lte(var2));
                    }
                }
            }
        }
        return this;
    }

    @Override
    public Query getQuery() {
        criterias.forEach(query::addCriteria);
        return query;
    }

    @Override
    public T query() {
        arrayConstructor();
        return queryOne(this.query);
    }
    @Override
    public List<T> list() {
        arrayConstructor();
        return list(this.query);
    }

    @Override
    public T one() {
        return queryOne(new Query());
    }

    private void arrayConstructor(){
        if (!criterias.isEmpty()){
            Criteria[] criteria = new Criteria[criterias.size()];
            for (int i = 0; i < criterias.size(); i++) {
                criteria[i] = criterias.get(i);
            }
            query.addCriteria(new Criteria().andOperator(criteria));
        }
    }


    @Override
    public LambdaQuery<T> eq(SFunction<T, ?> function, Object value) {
        return eq(true, function, value);
    }

    @Override
    public LambdaQuery<T> eq(boolean condition, SFunction<T, ?> function, Object value) {
        if (condition) {
            Field field = findField(function);
            Assert.notNull(field, "无法获取引用字段");
            Criteria criteria = Criteria.where(field.getName()).is(value);
            setCriteria(criteria);

        }
        return this;
    }

    @Override
    public LambdaQuery<T> in(SFunction<T, ?> function, Object... value) {
        return in(true, function, value);
    }

    @Override
    public LambdaQuery<T> in(boolean condition, SFunction<T, ?> function, Object... value) {
        if (condition) {
            Field field = findField(function);
            Assert.notNull(field, "无法获取引用字段");
            Criteria criteria = Criteria.where(field.getName()).in(value);
            setCriteria(criteria);
        }
        return this;
    }

    @Override
    public LambdaQuery<T> in(SFunction<T, ?> function, Collection<?> value) {
        return in(true, function, value);
    }

    @Override
    public LambdaQuery<T> in(boolean condition, SFunction<T, ?> function, Collection<?> value) {
        if (condition) {
            Field field = findField(function);
            Assert.notNull(field, "无法获取引用字段");
            Criteria criteria = Criteria.where(field.getName()).in(value);
            setCriteria(criteria);
        }
        return this;
    }

    @Override
    public LambdaQuery<T> lt(SFunction<T, ?> function, Object value) {
        return lt(true, function, value);
    }

    @Override
    public LambdaQuery<T> lt(boolean condition, SFunction<T, ?> function, Object value) {
        if (condition) {
            Field field = findField(function);
            Assert.notNull(field, "无法获取引用字段");
            Criteria criteria = Criteria.where(field.getName()).lt(value);
            setCriteria(criteria);
        }
        return this;
    }

    @Override
    public LambdaQuery<T> gt(SFunction<T, ?> function, Object value) {
        return gt(true, function, value);
    }

    @Override
    public LambdaQuery<T> gt(boolean condition, SFunction<T, ?> function, Object value) {
        if (condition) {
            Field field = findField(function);
            Assert.notNull(field, "无法获取引用字段");
            Criteria criteria = Criteria.where(field.getName()).gt(value);
            setCriteria(criteria);
        }
        return this;
    }

    @Override
    public LambdaQuery<T> lte(SFunction<T, ?> function, Object value) {
        return lte(true, function, value);
    }

    @Override
    public LambdaQuery<T> lte(boolean condition, SFunction<T, ?> function, Object value) {
        if (condition) {
            Field field = findField(function);
            Assert.notNull(field, "无法获取引用字段");
            Criteria criteria = Criteria.where(field.getName()).lte(value);
            setCriteria(criteria);
        }
        return this;
    }

    @Override
    public LambdaQuery<T> gte(SFunction<T, ?> function, Object value) {
        return gte(true, function, value);
    }

    @Override
    public LambdaQuery<T> gte(boolean condition, SFunction<T, ?> function, Object value) {
        if (condition) {
            Field field = findField(function);
            Assert.notNull(field, "无法获取引用字段");
            Criteria criteria = Criteria.where(field.getName()).gte(value);
            setCriteria(criteria);
        }
        return this;
    }

    @Override
    public LambdaQuery<T> not(SFunction<T, ?> function, Object value) {
        return not(true, function, value);
    }

    @Override
    public LambdaQuery<T> not(boolean condition, SFunction<T, ?> function, Object value) {
        if (condition) {
            Field field = findField(function);
            Assert.notNull(field, "无法获取引用字段");
            Criteria criteria = Criteria.where(field.getName()).ne(value);
            setCriteria(criteria);
        }
        return this;
    }

    @Override
    public LambdaQuery<T> allEq(SFunction<T, ?> function, Collection<?> value) {
        return allEq(true, function, value);
    }

    @Override
    public LambdaQuery<T> allEq(boolean condition, SFunction<T, ?> function, Collection<?> value) {
        if (condition) {
            Field field = findField(function);
            Assert.notNull(field, "无法获取引用字段");
            Criteria criteria = Criteria.where(field.getName()).all(value);
            setCriteria(criteria);
        }
        return this;
    }

    @Override
    public LambdaQuery<T> elemMatch(SFunction<T, ?> function, Criteria criteria) {
        return elemMatch(true, function, criteria);
    }

    @Override
    public LambdaQuery<T> elemMatch(boolean condition, SFunction<T, ?> function, Criteria criteria) {
        if (condition) {
            Field field = findField(function);
            Assert.notNull(field, "无法获取引用字段");
            Criteria cri = Criteria.where(field.getName()).elemMatch(criteria);
            setCriteria(criteria);
        }
        return this;
    }

    @Override
    public LambdaQuery<T> like(boolean condition, SFunction<T, ?> function, Object value, String regex) {
        if (condition) {
            Field field = findField(function);
            Assert.notNull(field, "无法获取引用字段");
            Pattern pattern;
            if (Objects.nonNull(regex)) {
                pattern = Pattern.compile(regex, Pattern.CASE_INSENSITIVE);
            } else {
                pattern = Pattern.compile("^.*" + value + ".*$", Pattern.CASE_INSENSITIVE);
            }

            Criteria criteria = Criteria.where(field.getName()).regex(pattern);
            setCriteria(criteria);
        }
        return this;
    }

    @Override
    public LambdaQuery<T> like(SFunction<T, ?> function, Object value) {
        return like(true, function, value,null);
    }

    @Override
    public PageImpl<T> page(PageRequest pageRequest) {
        return page(query, pageRequest);
    }

    public LambdaQuery<T> maxTime(Duration duration) {
        this.query.maxTime(duration);
        return this;
    }

    private void setCriteria(Criteria criteria){
        criterias.add(criteria);
    }

}
